package cn.yh.nft.turbo.lock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName: DistributeLockAspect
 * @Author: 草莓爱上啵啵
 *
 *   分布式锁切面
 */
@Aspect
@Component
public class DistributeLockAspect {

    private RedissonClient redissonClient;

    public DistributeLockAspect(RedissonClient redissonClient){
        this.redissonClient = redissonClient;
    }


    public static final Logger LOG = LoggerFactory.getLogger(DistributeLockAspect.class);

            /**
         * 环绕通知，用于处理分布式锁
         * 该方法在带有 DistributeLock 注解的方法执行前后被调用
         * 它的主要职责是获取锁、执行方法、释放锁
         *
         * @param pjp 切入点，提供了关于方法执行的上下文信息
         * @return 方法执行的结果
         * @throws Exception 如果方法执行或锁操作失败，则抛出异常
         */
        @Around("@annotation(cn.yh.nft.turbo.lock.DistributeLock)")
        public Object process(ProceedingJoinPoint pjp) throws Exception {
            Object response = null;
            // 获取被拦截方法的反射对象
            Method method =((MethodSignature) pjp.getSignature()).getMethod();

            // 获取方法上的 DistributeLock 注解
            DistributeLock distributeLock = method.getAnnotation(DistributeLock.class);

            // 获取锁的键
            String key = distributeLock.key();

            // 如果锁键未指定，则尝试通过 SpEL 表达式获取
            if(DistributeLockConstant.NONE_KEY.equals(key)){
                if (DistributeLockConstant.NONE_KEY.equals(distributeLock.keyExpression())){
                    throw new DistributeLockException("no lock key found...");
                }

                // 解析 SpEL 表达式
                SpelExpressionParser parser = new SpelExpressionParser();
                Expression expression = parser.parseExpression(distributeLock.keyExpression());

                // 创建评估上下文，并将方法参数值绑定到其中
                EvaluationContext context = new StandardEvaluationContext();
                Object[] args = pjp.getArgs();
                StandardReflectionParameterNameDiscoverer discoverer =
                        new StandardReflectionParameterNameDiscoverer();
                String[] parameterNames = discoverer.getParameterNames(method);
                if (parameterNames != null){
                    for (int i =0; i< parameterNames.length; i++){
                        context.setVariable(parameterNames[i], args[i]);
                    }
                }

                // 通过评估上下文解析 SpEL 表达式，获取锁键
                key = String.valueOf(expression.getValue(context));
            }

            // 获取场景标识，并与锁键组合形成完整的锁键
            String scene = distributeLock.scene();
            String lockKey = scene + "#" + key;

            // 获取锁的过期时间和等待时间
            int expireTime = distributeLock.expireTime();
            int waitTime = distributeLock.waitTime();

            // 获取 Redis 分布式锁实例
            RLock rLock = redissonClient.getLock(lockKey);
            boolean lockResult = false;

            // 根据不同的等待时间和过期时间策略，尝试获取锁
            if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME){
                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME){
                    LOG.info(String.format("lock for key : %s", lockKey));
                    rLock.lock();
                }else {
                    LOG.info(String.format("lock for key : %s , expire : %s", lockKey, expireTime));
                    rLock.lock(expireTime, TimeUnit.MILLISECONDS);
                }
                lockResult = true;
            }else {
                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME){
                    LOG.info(String.format("try lock for key : %s , wait : %s", lockKey, waitTime));
                    lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
                }else {
                    LOG.info(String.format("try lock for key : %s , expire : %s , wait : %s", lockKey, expireTime, waitTime));
                    lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
                }
            }

            // 如果未能获取锁，则抛出异常
            if (!lockResult){
                LOG.warn(String.format("lock failed for key : %s , expire : %s", lockKey, expireTime));
                throw new DistributeLockException("acquire lock failed... key : " + lockKey);
            }

            try{
                // 执行被拦截的方法
                LOG.info(String.format("lock success for key : %s , expire : %s", lockKey, expireTime));
                response = pjp.proceed();
            }catch (Throwable e){
                throw new Exception(e);
            }finally {
                // 释放锁
                rLock.unlock();
                LOG.info(String.format("unlock for key : %s , expire : %s", lockKey, expireTime));
            }

            return response;
        }

}
